<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JSONL文件处理工具</title>
  <!-- 引入Tailwind CSS -->
  <script src="https://cdn.tailwindcss.com"></script>
  <!-- 引入Font Awesome -->
  <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
  <!-- 引入jQuery -->
  <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
  
  <!-- Tailwind配置 -->
  <script>
    tailwind.config = {
      theme: {
        extend: {
          colors: {
            primary: '#165DFF',
            secondary: '#6B7280',
            success: '#36D399',
            warning: '#FFAA33',
            danger: '#F87272',
            dark: '#1F2937',
            light: '#F9FAFB'
          },
          fontFamily: {
            sans: ['Inter', 'system-ui', 'sans-serif'],
          },
        },
      }
    }
  </script>
  
  <!-- 自定义工具类 -->
  <style type="text/tailwindcss">
    @layer utilities {
      .content-auto {
        content-visibility: auto;
      }
      .card-shadow {
        box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
      }
      .transition-all-300 {
        transition: all 300ms ease-in-out;
      }
    }
  </style>
</head>
<body class="bg-gray-50 font-sans text-dark min-h-screen flex flex-col">
  <!-- 顶部导航栏 -->
  <header class="bg-white shadow-sm sticky top-0 z-50">
    <div class="container mx-auto px-4 py-4 flex justify-between items-center">
      <div class="flex items-center space-x-2">
        <i class="fa fa-file-code-o text-primary text-2xl"></i>
        <h1 class="text-xl font-bold text-primary">JSONL文件处理工具</h1>
      </div>
      <div class="hidden md:flex items-center space-x-4">
        <button id="theme-toggle" class="p-2 rounded-full hover:bg-gray-100 transition-all-300">
          <i class="fa fa-moon-o text-secondary"></i>
        </button>
        <button class="bg-primary/10 text-primary px-4 py-2 rounded-lg hover:bg-primary/20 transition-all-300">
          <i class="fa fa-question-circle mr-1"></i>帮助
        </button>
      </div>
    </div>
  </header>

  <!-- 主要内容区 -->
  <main class="flex-grow container mx-auto px-4 py-8">
    <!-- 文件上传区域 -->
    <section class="mb-8">
      <div class="bg-white rounded-xl p-6 card-shadow">
        <h2 class="text-lg font-semibold mb-4 flex items-center">
          <i class="fa fa-upload text-primary mr-2"></i>文件上传
        </h2>
        <div id="drop-area" class="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-primary transition-all-300 cursor-pointer">
          <input type="file" id="file-input" class="hidden" accept=".jsonl,.json">
          <i class="fa fa-file-text-o text-4xl text-gray-400 mb-3"></i>
          <p class="text-gray-500 mb-2">拖放JSONL或JSON文件到此处，或</p>
          <button id="select-file-btn" class="bg-primary text-white px-4 py-2 rounded-lg hover:bg-primary/90 transition-all-300">
            选择文件
          </button>
          <p class="text-xs text-gray-400 mt-3">支持的文件格式：.jsonl, .json</p>
        </div>
        <div id="file-info" class="mt-4 hidden">
          <div class="flex items-center justify-between bg-gray-50 p-3 rounded-lg">
            <div class="flex items-center">
              <i class="fa fa-file-text-o text-primary mr-2"></i>
              <div>
                <p id="file-name" class="font-medium"></p>
                <p id="file-size" class="text-xs text-gray-500"></p>
              </div>
            </div>
            <button id="remove-file" class="text-gray-400 hover:text-danger transition-all-300">
              <i class="fa fa-times"></i>
            </button>
          </div>
        </div>
      </div>
    </section>

    <!-- 数据统计区域 -->
    <section id="stats-section" class="mb-8 hidden">
      <div class="bg-white rounded-xl p-6 card-shadow">
        <h2 class="text-lg font-semibold mb-4 flex items-center">
          <i class="fa fa-bar-chart text-primary mr-2"></i>数据统计
        </h2>
        <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
          <div class="bg-blue-50 rounded-lg p-4 flex items-center">
            <div class="w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center mr-4">
              <i class="fa fa-list text-primary"></i>
            </div>
            <div>
              <p class="text-sm text-gray-500">总条目数</p>
              <p id="total-count" class="text-xl font-bold text-primary">0</p>
            </div>
          </div>
          <div class="bg-green-50 rounded-lg p-4 flex items-center">
            <div class="w-12 h-12 bg-success/10 rounded-full flex items-center justify-center mr-4">
              <i class="fa fa-eye text-success"></i>
            </div>
            <div>
              <p class="text-sm text-gray-500">显示条目</p>
              <p id="visible-count" class="text-xl font-bold text-success">0</p>
            </div>
          </div>
          <div class="bg-orange-50 rounded-lg p-4 flex items-center">
            <div class="w-12 h-12 bg-warning/10 rounded-full flex items-center justify-center mr-4">
              <i class="fa fa-eye-slash text-warning"></i>
            </div>
            <div>
              <p class="text-sm text-gray-500">隐藏条目</p>
              <p id="hidden-count" class="text-xl font-bold text-warning">0</p>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- 操作区域 -->
    <section id="actions-section" class="mb-8 hidden">
      <div class="bg-white rounded-xl p-6 card-shadow">
        <h2 class="text-lg font-semibold mb-4 flex items-center">
          <i class="fa fa-cogs text-primary mr-2"></i>批量操作
        </h2>
        <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
          <div>
            <p class="text-sm text-gray-600 mb-3">设置指定范围的条目隐藏状态</p>
            <div class="flex flex-wrap gap-3">
              <div class="flex-1">
                <label for="start-index" class="block text-sm font-medium text-gray-700 mb-1">起始位置</label>
                <input type="number" id="start-index" min="0" value="0" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none transition-all-300">
              </div>
              <div class="flex-1">
                <label for="end-index" class="block text-sm font-medium text-gray-700 mb-1">结束位置</label>
                <input type="number" id="end-index" min="0" value="50" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none transition-all-300">
              </div>
            </div>
            <div class="flex flex-wrap gap-3 mt-4">
              <button id="set-hidden-btn" class="flex-1 bg-warning text-white px-4 py-2 rounded-lg hover:bg-warning/90 transition-all-300 flex items-center justify-center">
                <i class="fa fa-eye-slash mr-1"></i>设为隐藏
              </button>
              <button id="set-visible-btn" class="flex-1 bg-success text-white px-4 py-2 rounded-lg hover:bg-success/90 transition-all-300 flex items-center justify-center">
                <i class="fa fa-eye mr-1"></i>设为显示
              </button>
            </div>
          </div>
          <div>
            <p class="text-sm text-gray-600 mb-3">快速操作所有条目</p>
            <div class="grid grid-cols-1 gap-3">
              <button id="hide-all-btn" class="w-full bg-warning/20 text-warning px-4 py-3 rounded-lg hover:bg-warning/30 transition-all-300 flex items-center justify-center">
                <i class="fa fa-eye-slash mr-2"></i>全部隐藏
              </button>
              <button id="show-all-btn" class="w-full bg-success/20 text-success px-4 py-3 rounded-lg hover:bg-success/30 transition-all-300 flex items-center justify-center">
                <i class="fa fa-eye mr-2"></i>全部显示
              </button>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- 数据预览区域 -->
    <section id="preview-section" class="mb-8 hidden">
      <div class="bg-white rounded-xl p-6 card-shadow">
        <div class="flex justify-between items-center mb-4">
          <h2 class="text-lg font-semibold flex items-center">
            <i class="fa fa-table text-primary mr-2"></i>数据预览
          </h2>
          <div class="flex items-center space-x-2">
            <div class="relative">
              <input type="text" id="search-input" placeholder="搜索..." class="pl-8 pr-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none transition-all-300">
              <i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
            </div>
            <div class="relative">
              <select id="filter-select" class="pl-3 pr-8 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none transition-all-300 appearance-none bg-white">
                <option value="all">全部</option>
                <option value="visible">显示</option>
                <option value="hidden">隐藏</option>
              </select>
              <i class="fa fa-chevron-down absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 pointer-events-none"></i>
            </div>
          </div>
        </div>
        <div class="overflow-x-auto">
          <table class="min-w-full divide-y divide-gray-200">
            <thead class="bg-gray-50">
              <tr>
                <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                  序号
                </th>
                <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                  发送者
                </th>
                <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                  发送时间
                </th>
                <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                  内容
                </th>
                <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                  状态
                </th>
                <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                  操作
                </th>
              </tr>
            </thead>
            <tbody id="data-table-body" class="bg-white divide-y divide-gray-200">
              <!-- 数据将通过JavaScript动态填充 -->
            </tbody>
          </table>
        </div>
        <div id="no-data" class="py-12 text-center hidden">
          <i class="fa fa-inbox text-gray-300 text-5xl mb-3"></i>
          <p class="text-gray-500">暂无数据</p>
        </div>
        <div id="loading-data" class="py-12 text-center hidden">
          <div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
          <p class="text-gray-500 mt-2">加载中...</p>
        </div>
        <div id="pagination" class="flex justify-between items-center mt-6">
          <div class="text-sm text-gray-500">
            显示 <span id="showing-range">0-0</span> 条，共 <span id="total-items">0</span> 条
          </div>
          <div class="flex space-x-1">
            <button id="prev-page" class="px-3 py-1 rounded border border-gray-300 text-gray-600 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-all-300" disabled>
              <i class="fa fa-chevron-left"></i>
            </button>
            <button id="next-page" class="px-3 py-1 rounded border border-gray-300 text-gray-600 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-all-300" disabled>
              <i class="fa fa-chevron-right"></i>
            </button>
          </div>
        </div>
      </div>
    </section>

    <!-- 导出区域 -->
    <section id="export-section" class="mb-8 hidden">
      <div class="bg-white rounded-xl p-6 card-shadow">
        <h2 class="text-lg font-semibold mb-4 flex items-center">
          <i class="fa fa-download text-primary mr-2"></i>导出处理后的文件
        </h2>
        <div class="flex flex-wrap gap-4">
          <div class="flex-1">
            <label for="output-filename" class="block text-sm font-medium text-gray-700 mb-1">文件名</label>
            <input type="text" id="output-filename" value="processed.jsonl" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none transition-all-300">
          </div>
          <button id="export-btn" class="bg-primary text-white px-6 py-2 rounded-lg hover:bg-primary/90 transition-all-300 flex items-center justify-center">
            <i class="fa fa-download mr-1"></i>导出文件
          </button>
        </div>
      </div>
    </section>
  </main>

  <!-- 页脚 -->
  <footer class="bg-white border-t border-gray-200 py-6">
    <div class="container mx-auto px-4">
      <div class="flex flex-col md:flex-row justify-between items-center">
        <div class="text-center md:text-left mb-4 md:mb-0">
          <p class="text-gray-500 text-sm">© 2025 JSONL文件处理工具. 保留所有权利.</p>
        </div>
        <div class="flex space-x-4">
          <a href="#" class="text-gray-400 hover:text-primary transition-all-300">
            <i class="fa fa-github text-lg"></i>
          </a>
          <a href="#" class="text-gray-400 hover:text-primary transition-all-300">
            <i class="fa fa-twitter text-lg"></i>
          </a>
          <a href="#" class="text-gray-400 hover:text-primary transition-all-300">
            <i class="fa fa-linkedin text-lg"></i>
          </a>
        </div>
      </div>
    </div>
  </footer>

  <!-- 通知组件 -->
  <div id="notification" class="fixed bottom-4 right-4 max-w-xs bg-white rounded-lg shadow-lg p-4 transform translate-y-16 opacity-0 transition-all duration-300 z-50">
    <div class="flex items-start">
      <div id="notification-icon" class="flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center mr-3 bg-primary/10 text-primary">
        <i class="fa fa-info"></i>
      </div>
      <div class="ml-3">
        <h3 id="notification-title" class="text-sm font-medium text-gray-900">通知标题</h3>
        <div id="notification-message" class="mt-1 text-sm text-gray-500">通知内容</div>
      </div>
      <button id="close-notification" class="ml-auto flex-shrink-0 text-gray-400 hover:text-gray-600">
        <i class="fa fa-times"></i>
      </button>
    </div>
  </div>

  <!-- 主要脚本 -->
  <script>
    // 全局变量
    let jsonlData = [];
    let processedData = [];
    let fileHeader = null;
    let fileInfo = null;
    let currentPage = 1;
    let itemsPerPage = 10;
    let filteredData = [];
    let isJsonFormat = false;
    
    // DOM元素
    const dropArea = document.getElementById('drop-area');
    const fileInput = document.getElementById('file-input');
    const selectFileBtn = document.getElementById('select-file-btn');
    const fileInfoEl = document.getElementById('file-info');
    const fileNameEl = document.getElementById('file-name');
    const fileSizeEl = document.getElementById('file-size');
    const removeFileBtn = document.getElementById('remove-file');
    const statsSection = document.getElementById('stats-section');
    const totalCountEl = document.getElementById('total-count');
    const visibleCountEl = document.getElementById('visible-count');
    const hiddenCountEl = document.getElementById('hidden-count');
    const actionsSection = document.getElementById('actions-section');
    const startIndexInput = document.getElementById('start-index');
    const endIndexInput = document.getElementById('end-index');
    const setHiddenBtn = document.getElementById('set-hidden-btn');
    const setVisibleBtn = document.getElementById('set-visible-btn');
    const hideAllBtn = document.getElementById('hide-all-btn');
    const showAllBtn = document.getElementById('show-all-btn');
    const previewSection = document.getElementById('preview-section');
    const dataTableBody = document.getElementById('data-table-body');
    const noDataEl = document.getElementById('no-data');
    const loadingDataEl = document.getElementById('loading-data');
    const showingRangeEl = document.getElementById('showing-range');
    const totalItemsEl = document.getElementById('total-items');
    const prevPageBtn = document.getElementById('prev-page');
    const nextPageBtn = document.getElementById('next-page');
    const searchInput = document.getElementById('search-input');
    const filterSelect = document.getElementById('filter-select');
    const exportSection = document.getElementById('export-section');
    const outputFilenameInput = document.getElementById('output-filename');
    const exportBtn = document.getElementById('export-btn');
    const notification = document.getElementById('notification');
    const notificationIcon = document.getElementById('notification-icon');
    const notificationTitle = document.getElementById('notification-title');
    const notificationMessage = document.getElementById('notification-message');
    const closeNotification = document.getElementById('close-notification');
    
    // 初始化
    document.addEventListener('DOMContentLoaded', function() {
      // 文件上传相关事件
      selectFileBtn.addEventListener('click', () => fileInput.click());
      fileInput.addEventListener('change', handleFileSelect);
      
      // 拖放相关事件
      ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
        dropArea.addEventListener(eventName, preventDefaults, false);
      });
      
      function preventDefaults(e) {
        e.preventDefault();
        e.stopPropagation();
      }
      
      ['dragenter', 'dragover'].forEach(eventName => {
        dropArea.addEventListener(eventName, highlight, false);
      });
      
      ['dragleave', 'drop'].forEach(eventName => {
        dropArea.addEventListener(eventName, unhighlight, false);
      });
      
      function highlight() {
        dropArea.classList.add('border-primary');
        dropArea.classList.add('bg-primary/5');
      }
      
      function unhighlight() {
        dropArea.classList.remove('border-primary');
        dropArea.classList.remove('bg-primary/5');
      }
      
      dropArea.addEventListener('drop', handleDrop, false);
      
      function handleDrop(e) {
        const dt = e.dataTransfer;
        const files = dt.files;
        
        if (files.length) {
          handleFiles(files);
        }
      }
      
      // 移除文件
      removeFileBtn.addEventListener('click', removeFile);
      
      // 批量操作按钮
      setHiddenBtn.addEventListener('click', () => setVisibilityRange(true));
      setVisibleBtn.addEventListener('click', () => setVisibilityRange(false));
      hideAllBtn.addEventListener('click', () => setAllVisibility(true));
      showAllBtn.addEventListener('click', () => setAllVisibility(false));
      
      // 分页按钮
      prevPageBtn.addEventListener('click', goToPrevPage);
      nextPageBtn.addEventListener('click', goToNextPage);
      
      // 搜索和过滤
      searchInput.addEventListener('input', filterData);
      filterSelect.addEventListener('change', filterData);
      
      // 导出文件
      exportBtn.addEventListener('click', exportFile);
      
      // 关闭通知
      closeNotification.addEventListener('click', hideNotification);
    });
    
    // 处理文件选择
    function handleFileSelect(e) {
      const files = e.target.files;
      if (files.length) {
        handleFiles(files);
      }
    }
    
    // 处理文件
    function handleFiles(files) {
      const file = files[0];
      const fileExtension = file.name.split('.').pop().toLowerCase();
      
      if (!['jsonl', 'json'].includes(fileExtension)) {
        showNotification('错误', '请选择JSONL或JSON格式的文件', 'error');
        return;
      }
      
      isJsonFormat = fileExtension === 'json';
      
      fileInfo = {
        name: file.name,
        size: formatFileSize(file.size)
      };
      
      // 显示文件信息
      fileNameEl.textContent = fileInfo.name;
      fileSizeEl.textContent = fileInfo.size;
      fileInfoEl.classList.remove('hidden');
      
      // 读取文件内容
      const reader = new FileReader();
      reader.onload = function(e) {
        const content = e.target.result;
        processFileContent(content, isJsonFormat);
      };
      reader.onerror = function() {
        showNotification('错误', '读取文件时发生错误', 'error');
      };
      reader.readAsText(file);
    }
    
    // 处理文件内容
    function processFileContent(content, isJsonFormat) {
      showLoading(true);
      
      setTimeout(() => {
        try {
          if (isJsonFormat) {
            // 处理JSON格式
            const jsonData = JSON.parse(content);
            
            // 检查是否包含聊天记录和头部信息
            if (Array.isArray(jsonData)) {
              // 如果是纯数组格式，直接作为聊天记录
              fileHeader = null;
              jsonlData = jsonData;
            } else if (typeof jsonData === 'object' && jsonData !== null) {
              // 如果是包含头部和聊天记录的对象
              if (jsonData.header && jsonData.messages) {
                fileHeader = jsonData.header;
                jsonlData = jsonData.messages;
              } else {
                // 尝试查找可能的头部和消息结构
                const keys = Object.keys(jsonData);
                if (keys.length > 0) {
                  const firstKey = keys[0];
                  if (Array.isArray(jsonData[firstKey])) {
                    // 假设第一个数组是消息
                    fileHeader = { [firstKey]: 'Chat Messages' };
                    jsonlData = jsonData[firstKey];
                  } else {
                    // 假设整个对象是头部，没有消息
                    fileHeader = jsonData;
                    jsonlData = [];
                  }
                } else {
                  fileHeader = null;
                  jsonlData = [];
                }
              }
            } else {
              throw new Error('不支持的JSON格式');
            }
          } else {
            // 处理JSONL格式
            const lines = content.split('\n').filter(line => line.trim() !== '');
            
            if (lines.length > 0) {
              // 假设第一行为头部信息
              try {
                fileHeader = JSON.parse(lines[0]);
              } catch (e) {
                console.error('解析文件头时出错:', e);
                fileHeader = null;
              }
              
              // 其余行为聊天记录
              jsonlData = lines.slice(1).map(line => {
                try {
                  return JSON.parse(line);
                } catch (e) {
                  console.error('解析JSON行时出错:', e);
                  return null;
                }
              }).filter(item => item !== null);
            } else {
              fileHeader = null;
              jsonlData = [];
            }
          }
          
          // 初始化处理后的数据，默认所有条目都是可见的
          processedData = jsonlData.map(item => ({
            ...item,
            is_system: item.is_system || false
          }));
          
          // 更新统计信息
          updateStats();
          
          // 显示相关部分
          statsSection.classList.remove('hidden');
          actionsSection.classList.remove('hidden');
          previewSection.classList.remove('hidden');
          exportSection.classList.remove('hidden');
          
          // 初始化过滤后的数据
          filteredData = [...processedData];
          
          // 渲染表格
          renderTable();
          
          showNotification('成功', '文件已成功加载', 'success');
        } catch (e) {
          console.error('处理文件内容时出错:', e);
          showNotification('错误', '处理文件内容时发生错误', 'error');
        } finally {
          showLoading(false);
        }
      }, 500); // 添加延迟以显示加载状态
    }
    
    // 更新统计信息
    function updateStats() {
      const totalCount = processedData.length;
      const hiddenCount = processedData.filter(item => item.is_system).length;
      const visibleCount = totalCount - hiddenCount;
      
      totalCountEl.textContent = totalCount;
      visibleCountEl.textContent = visibleCount;
      hiddenCountEl.textContent = hiddenCount;
      
      totalItemsEl.textContent = filteredData.length;
    }
    
    // 渲染表格
    function renderTable() {
      const startIndex = (currentPage - 1) * itemsPerPage;
      const endIndex = Math.min(startIndex + itemsPerPage, filteredData.length);
      const currentItems = filteredData.slice(startIndex, endIndex);
      
      // 清空表格
      dataTableBody.innerHTML = '';
      
      // 渲染数据行
      if (currentItems.length > 0) {
        currentItems.forEach((item, index) => {
          const row = document.createElement('tr');
          row.className = 'hover:bg-gray-50 transition-all-300';
          
          // 格式化日期
          const date = item.send_date ? new Date(item.send_date).toLocaleString() : 'N/A';
          
          // 截断长文本
          const truncatedMes = item.mes && item.mes.length > 50 ? 
            item.mes.substring(0, 50) + '...' : 
            item.mes || 'N/A';
          
          // 状态标签
          const statusClass = item.is_system ? 'bg-warning/10 text-warning' : 'bg-success/10 text-success';
          const statusText = item.is_system ? '隐藏' : '显示';
          const statusIcon = item.is_system ? 'fa-eye-slash' : 'fa-eye';
          
          row.innerHTML = `
            <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
              ${startIndex + index + 1}
            </td>
            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
              ${item.name || 'N/A'}
            </td>
            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
              ${date}
            </td>
            <td class="px-6 py-4 text-sm text-gray-500 max-w-xs truncate">
              ${truncatedMes}
            </td>
            <td class="px-6 py-4 whitespace-nowrap">
              <span class="px-2 py-1 inline-flex text-xs leading-5 font-semibold rounded-full ${statusClass}">
                <i class="fa ${statusIcon} mr-1"></i>${statusText}
              </span>
            </td>
            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
              <button class="toggle-visibility text-primary hover:text-primary/80 transition-all-300" data-index="${processedData.indexOf(item)}">
                ${item.is_system ? '显示' : '隐藏'}
              </button>
            </td>
          `;
          
          dataTableBody.appendChild(row);
        });
        
        // 添加切换可见性的事件监听
        document.querySelectorAll('.toggle-visibility').forEach(btn => {
          btn.addEventListener('click', function() {
            const index = parseInt(this.getAttribute('data-index'));
            processedData[index].is_system = !processedData[index].is_system;
            updateStats();
            filterData();
            showNotification('成功', '条目状态已更新', 'success');
          });
        });
        
        noDataEl.classList.add('hidden');
      } else {
        noDataEl.classList.remove('hidden');
      }
      
      // 更新分页信息
      showingRangeEl.textContent = filteredData.length > 0 ? 
        `${startIndex + 1}-${endIndex}` : '0-0';
      
      // 更新分页按钮状态
      prevPageBtn.disabled = currentPage === 1;
      nextPageBtn.disabled = endIndex >= filteredData.length;
    }
    
    // 设置指定范围的可见性
    function setVisibilityRange(hidden) {
      const start = parseInt(startIndexInput.value) || 0;
      const end = parseInt(endIndexInput.value) || 0;
      
      if (start < 0 || end < start || end >= processedData.length) {
        showNotification('错误', '请输入有效的范围', 'error');
        return;
      }
      
      // 更新可见性
      for (let i = start; i <= end; i++) {
        processedData[i].is_system = hidden;
      }
      
      updateStats();
      filterData();
      showNotification('成功', `已将条目 ${start}-${end} 设置为${hidden ? '隐藏' : '显示'}`, 'success');
    }
    
    // 设置所有条目的可见性
    function setAllVisibility(hidden) {
      processedData.forEach(item => {
        item.is_system = hidden;
      });
      
      updateStats();
      filterData();
      showNotification('成功', `所有条目已设置为${hidden ? '隐藏' : '显示'}`, 'success');
    }
    
    // 上一页
    function goToPrevPage() {
      if (currentPage > 1) {
        currentPage--;
        renderTable();
      }
    }
    
    // 下一页
    function goToNextPage() {
      if ((currentPage * itemsPerPage) < filteredData.length) {
        currentPage++;
        renderTable();
      }
    }
    
    // 过滤数据
    function filterData() {
      const searchTerm = searchInput.value.toLowerCase().trim();
      const filterValue = filterSelect.value;
      
      filteredData = processedData.filter(item => {
        // 搜索过滤
        const matchesSearch = !searchTerm || 
          (item.name && item.name.toLowerCase().includes(searchTerm)) || 
          (item.mes && item.mes.toLowerCase().includes(searchTerm)) || 
          (item.send_date && new Date(item.send_date).toLocaleString().toLowerCase().includes(searchTerm));
        
        // 状态过滤
        const matchesFilter = filterValue === 'all' || 
          (filterValue === 'visible' && !item.is_system) || 
          (filterValue === 'hidden' && item.is_system);
        
        return matchesSearch && matchesFilter;
      });
      
      // 重置页码
      currentPage = 1;
      
      // 更新统计信息
      totalItemsEl.textContent = filteredData.length;
      
      // 重新渲染表格
      renderTable();
    }
    
    // 导出文件
    function exportFile() {
      if ((!processedData || processedData.length === 0) && !fileHeader) {
        showNotification('错误', '没有可导出的数据', 'error');
        return;
      }
      
      try {
        let content = '';
        const outputFilename = outputFilenameInput.value.trim() || 'processed.jsonl';
        const isJsonOutput = outputFilename.endsWith('.json');
        
        if (isJsonOutput) {
          // 导出为JSON格式
          if (fileHeader) {
            // 如果有头部信息，将头部和消息组合成一个对象
            content = JSON.stringify({
              header: fileHeader,
              messages: processedData
            }, null, 2);
          } else {
            // 如果没有头部信息，直接导出消息数组
            content = JSON.stringify(processedData, null, 2);
          }
        } else {
          // 导出为JSONL格式
          if (fileHeader) {
            // 添加头部信息作为第一行
            content += JSON.stringify(fileHeader) + '\n';
          }
          
          // 添加所有消息行
          processedData.forEach(item => {
            content += JSON.stringify(item) + '\n';
          });
        }
        
        // 创建Blob并下载
        const blob = new Blob([content], { type: isJsonOutput ? 'application/json' : 'application/jsonl' });
        const url = URL.createObjectURL(blob);
        
        const a = document.createElement('a');
        a.href = url;
        a.download = outputFilename;
        document.body.appendChild(a);
        a.click();
        
        setTimeout(() => {
          document.body.removeChild(a);
          URL.revokeObjectURL(url);
        }, 0);
        
        showNotification('成功', '文件已导出', 'success');
      } catch (e) {
        console.error('导出文件时出错:', e);
        showNotification('错误', '导出文件时发生错误', 'error');
      }
    }
    
    // 移除文件
    function removeFile() {
      fileInput.value = '';
      fileInfo = null;
      jsonlData = [];
      processedData = [];
      fileHeader = null;
      fileInfoEl.classList.add('hidden');
      statsSection.classList.add('hidden');
      actionsSection.classList.add('hidden');
      previewSection.classList.add('hidden');
      exportSection.classList.add('hidden');
      showNotification('信息', '文件已移除', 'info');
    }
    
    // 显示/隐藏加载状态
    function showLoading(show) {
      if (show) {
        loadingDataEl.classList.remove('hidden');
        dataTableBody.classList.add('hidden');
        noDataEl.classList.add('hidden');
      } else {
        loadingDataEl.classList.add('hidden');
        dataTableBody.classList.remove('hidden');
      }
    }
    
    // 显示通知
    function showNotification(title, message, type = 'info') {
      // 设置通知内容
      notificationTitle.textContent = title;
      notificationMessage.textContent = message;
      
      // 设置通知类型
      notificationIcon.className = 'flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center mr-3';
      
      switch (type) {
        case 'success':
          notificationIcon.classList.add('bg-success/10', 'text-success');
          notificationIcon.innerHTML = '<i class="fa fa-check"></i>';
          break;
        case 'error':
          notificationIcon.classList.add('bg-danger/10', 'text-danger');
          notificationIcon.innerHTML = '<i class="fa fa-times"></i>';
          break;
        case 'warning':
          notificationIcon.classList.add('bg-warning/10', 'text-warning');
          notificationIcon.innerHTML = '<i class="fa fa-exclamation-triangle"></i>';
          break;
        default:
          notificationIcon.classList.add('bg-primary/10', 'text-primary');
          notificationIcon.innerHTML = '<i class="fa fa-info"></i>';
      }
      
      // 显示通知
      notification.classList.remove('translate-y-16', 'opacity-0');
      
      // 自动关闭
      clearTimeout(window.notificationTimeout);
      window.notificationTimeout = setTimeout(hideNotification, 5000);
    }
    
    // 隐藏通知
    function hideNotification() {
      notification.classList.add('translate-y-16', 'opacity-0');
    }
    
    // 格式化文件大小
    function formatFileSize(bytes) {
      if (bytes === 0) return '0 Bytes';
      
      const k = 1024;
      const sizes = ['Bytes', 'KB', 'MB', 'GB'];
      const i = Math.floor(Math.log(bytes) / Math.log(k));
      
      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }
  </script>
</body>
</html>
    